#!/usr/bin/env python

import sys
import os.path
import os
import mimetypes;
try:
    import pygtk
    pygtk.require("2.0")
except:
    pass
import gtk
import gtk.glade
import gobject
import pango;
import gobject;
import thumbnails
import catalogs
import importer;
import exiftool;
import xmp;
import editors;
import threading;

class Item(object):
    
    def __init__(self,file):
        self.file = file;
        self.thumbnail = None;
        self.properties = None;
        self.position = None;
        
class GUI(object):
    """This is an Hello World GTK application"""
    
    lang = 'en';
    
    def __init__(self):
        
        self.itemsByFile = {};
        self.loadingFiles = False;
        self.updatingThumbnails = False;
        self.thumbnailer = thumbnails.Thumbnailer();
        self.thumbnailCache = thumbnails.ThumbnailCache(self.thumbnailer,128,96,500);
        self.thumbnailCache.connect("loaded",self.thumbnailLoaded);
        self.thumbnailCache.connect("removed",self.thumbnailRemoved);
        self.createImageLoadingThumbnail(128,96);
        
        #Set the Glade file
        self.wTree = gtk.glade.XML(os.path.join(os.path.dirname(__file__),"sprat.glade"));
        self.wTree.signal_autoconnect(self);
        
        #Get the Main Window, and connect the "destroy" event
        self.mainWindow = self.wTree.get_widget("mainWindow")
        self.thumbnailsView = self.wTree.get_widget("thumbnailsView");
        self.menuFileNew = self.wTree.get_widget("menuFileNew");
        self.menuFileOpen = self.wTree.get_widget("menuFileOpen");
        self.menuFileSave = self.wTree.get_widget("menuFileSave");
        self.menuFileSaveAs = self.wTree.get_widget("menuFileSaveAs");
        self.menuEditImportFolder = self.wTree.get_widget("submenuImportFolder");
        self.menuEditImportFiles = self.wTree.get_widget("submenuImportFolder");
        self.toolNew = self.wTree.get_widget("toolNew");
        self.toolOpen = self.wTree.get_widget("toolOpen");
        self.toolSave = self.wTree.get_widget("toolSave");
        self.toolImportFolder = self.wTree.get_widget("submenuToolImportFolder");
        self.toolImportFiles = self.wTree.get_widget("submenuToolImportFiles");
        self.toolRemove = self.wTree.get_widget("toolRemove");
        self.toolApply = self.wTree.get_widget("toolApply");
        self.toolCancel = self.wTree.get_widget("toolCancel");
        self.imageView = self.wTree.get_widget("imageView");
        self.imageNotebook = self.wTree.get_widget('imageNotebook');
        
        self.importDialog = self.wTree.get_widget('importDialog');
        self.importProgressBar = self.wTree.get_widget('importProgressBar');
        self.importStatus = self.wTree.get_widget('importStatus');
        self.importingFileName = self.wTree.get_widget('importingFileName');
        
        self.notebookTagger = self.wTree.get_widget('notebookTagger'); 

        self.currentThumbnailsRange = None;
        self.thumbnailsModel = gtk.ListStore( str, gtk.gdk.Pixbuf, object );
        self.thumbnailsView.set_model(self.thumbnailsModel);
        
        self.tagBrowserModel = gtk.TreeStore(object,str,int);
        self.tagBrowser = self.wTree.get_widget('tagBrowser');
        self.tagBrowser.set_model(self.tagBrowserModel);
        self.tagBrowser.get_selection().connect('changed',self.on_tagBrowser_selection_changed);

        cell = gtk.CellRendererText();
        cell.set_property('ellipsize',pango.ELLIPSIZE_END);
        column = gtk.TreeViewColumn('Tag', cell);
        self.tagBrowser.append_column(column);
        column.set_attributes(cell, text=1);
        column.set_expand(True);
        cell = gtk.CellRendererText();
        column = gtk.TreeViewColumn('Count', cell);
        self.tagBrowser.append_column(column);
        column.set_attributes(cell, text=2);
        
        self.tagEditorScroller = self.wTree.get_widget('tagEditorScroller');
        self.tagEditor = gtk.VBox(homogeneous=False, spacing=0);
        self.tagEditorScroller.add_with_viewport(self.tagEditor);
        
        self.propertiesEditor = self.createAnnotationEditor(self.tagEditor,'Properties');
        self.exifEditor = self.createAnnotationEditor(self.tagEditor,'EXIF');
        self.annotationsEditor = self.createAnnotationEditor(self.tagEditor,'Annotations');

        self.tagEditor.show_all();
        
        self.configureAnnotationEditor();

        self.thumbnailsView.get_parent().get_vscrollbar().get_adjustment().connect('changed',self.on_thumbnailsView_scrolled);
        self.thumbnailsView.get_parent().get_vscrollbar().get_adjustment().connect('value-changed',self.on_thumbnailsView_scrolled);
        self.thumbnailsView.get_parent().get_vscrollbar().set_update_policy(gtk.UPDATE_CONTINUOUS);

        self.currentFolder = os.path.expanduser('~');
        
        self.catalog = None;
        
    def createAnnotationEditor(self,parent,title):
        grid = gtk.Table(1,4);
        grid.set_col_spacing(1,4);
        expander = gtk.Expander(label=title);
        expander.add(grid);
        expander.set_expanded(True);
        parent.pack_start(expander,expand=False,fill=False,padding=0);
        return grid;

    def on_annotation_edited(self,cellRenderer,path,text,model):
        iter = model.get_iter(path);
        model.set_value(iter,1,text);
        
    def on_tagEditor_cursor_changed(self,treeView,cellRenderer):
        if not self.on_annotation_edited_entered:
            path,column = treeView.get_cursor();
            column = treeView.get_column(2);
            gobject.timeout_add(100, lambda t, p, c: self.forceEdit(t, p, c),treeView, path, column)
            
    def forceEdit(self,treeView,path,column):
        self.on_annotation_edited_entered = True;
        treeView.set_cursor(path, column, True);
        self.on_annotation_edited_entered = False;
    
    def configureAnnotationEditor(self):
        self.propertyEditors = {};
        self.addPropertyEditor('photoshop:Headline',False);
        self.addPropertyEditor('dc:title',False);
        self.addPropertyEditor('mediapro:Event',True);
        self.addPropertyEditor('mediapro:People',True);
        self.addPropertyEditor('photoshop:DateCreated',False);
        self.addPropertyEditor('dc:creator',True);
        self.addPropertyEditor('dc:subject',True);
        self.addPropertyEditor('Iptc4xmpCore:Scene',True);
        self.addPropertyEditor('photoshop:Category',True);
        self.addPropertyEditor('photoshop:SupplementalCategories',True);
        self.addPropertyEditor('photoshop:City',True);
        self.addPropertyEditor('photoshop:Country',True);
        self.addPropertyEditor('photoshop:State',True);
        
        
#        descriptionProperty = xmp.getProperty('dc:description');
#        self.configurePropertyEditorWidget(self.wTree.get_widget('descriptionEditor').get_buffer(),descriptionProperty);
        
        self.annotationsEditor.show();
        
    def addPropertyEditor(self,tag,useDictionary):
        prop = xmp.getProperty(tag);
        if prop != None:
            propertyEditor = editors.createPropertyEditor(prop.typedecl,self.lang,prop.getLabelOrName(self.lang));
            self.propertyEditors[tag] = propertyEditor;
            propertyEditor.connect('reconfigure', self.onPropertyEditoReconfigure);
            propertyEditor.connect('dirty',self.on_propertyEditorDirty);
            
    def onPropertyEditoReconfigure(self,propertyEditor):
        self.configurePropertyEditors();
            
    def configurePropertyEditorWidget(self,widget,property):
        widget.connect('changed',self.on_propertyEditor_changed,property);
        self.propertyEditors[property.getPrefixedName()] = widget;
    
    def thumbnailLoaded(self,cache,file,pixbuf):
        item = self.itemsByFile.get(file);
        if item != None:
            iter = self.thumbnailsModel.get_iter(item.position);
            item.thumbnail = pixbuf;
            if iter != None:
                self.thumbnailsModel.set_value(iter,1,pixbuf);
#                print "Loaded: ["+str(item.position)+"] "+str(file);
            else:
                print "Not found:" +file;
        else:
            print "Not found:" +file;
            
    def thumbnailRemoved(self,cache,file):
        item = self.itemsByFile.get(file);
        if item != None:
            iter = self.thumbnailsModel.get_iter(item.position);
            item.thumbnail = None;
            if iter != None:
                self.thumbnailsModel.set_value(iter,1,self.imageLoadingThumbnail);
            
    def setCatalog(self,catalog):
        self.__catalog = catalog;
        if catalog == None:
            self.mainWindow.set_title('Gnome Pthoto Tagger');
        else:
            self.mainWindow.set_title(os.path.basename(catalog.path));
        self.toolImportFolder.set_sensitive(catalog != None);
        self.toolImportFiles.set_sensitive(catalog != None);
        self.toolRemove.set_sensitive(catalog != None);
        self.thumbnailer.catalog = catalog;
        self.buildTagBrowserModel();
        self.home();
        for property,propertyEditor in self.propertyEditors.iteritems():
            propertyEditor.setCatalog(self.catalog);
        if catalog != None:
            self.queueFilesWithoutThumbnails();
                
    def queueFilesWithoutThumbnails(self):
        for file, in self.catalog.getFilesWithoutThumbnail():
            self.thumbnailer.loadThumbnail(
                file,
                self.catalog.getIntOption(catalogs.OPTION_MAX_WIDTH),
                self.catalog.getIntOption(catalogs.OPTION_MAX_HEIGHT));
    
    def getCatalog(self):
        return self.__catalog;
            
    catalog = property(getCatalog,setCatalog);
    
    def on_thumbnailsView_selection_changed(self,widget):
        self.imagePixbuf = None;
        self.updatePropertyEditorValues();
            
    def updatePropertyEditorValues(self):
        file = self.getCursorFile();
        files = self.getSelectedFiles();
        position = 0;
        for (property,editor) in self.propertyEditors.iteritems():
            if self.catalog == None or file == None:
                editor.setValue(None);
                editor.setSensitive(False);
            else:
                value = self.catalog.getTag(file, property, "");
                if len(files) == 1:
                    editor.setValue(value);
                else:
                    editor.setValues(value,[ self.catalog.getTag(f, property, "") for f in files ]);
                editor.setSensitive(True);
        self.configurePropertyEditors();
        self.setDirty(False);
    
    def configurePropertyEditors(self):
        position = 0;
        for propertyEditor in self.propertyEditors.itervalues():
            position = propertyEditor.configure(self.annotationsEditor,position);
                    
    def getCursorFile(self):
        cursor = self.thumbnailsView.get_cursor();
        if cursor != None and cursor[0] != None:
            return self.thumbnailsModel.get_value(self.thumbnailsModel.get_iter(cursor[0]),2).file;
        return None;
    
    def getSelectedFiles(self):
        paths = self.thumbnailsView.get_selected_items();
        return [ self.thumbnailsModel.get_value(self.thumbnailsModel.get_iter(path[0]),2).file for path in paths ];
                    
    def fileOpen(self,widget):
        openCatalogDialog = gtk.FileChooserDialog("Open catalog",
                            self.mainWindow,
                            gtk.FILE_CHOOSER_ACTION_OPEN,
                            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                             gtk.STOCK_OPEN, gtk.RESPONSE_OK));
        openCatalogDialog.set_current_folder(self.currentFolder);
        if openCatalogDialog.run() == gtk.RESPONSE_OK:
            self.currentFolder = openCatalogDialog.get_current_folder();
            file = openCatalogDialog.get_filename();
            if os.path.exists(file):
                self.catalog = catalogs.Catalog(file);
        openCatalogDialog.destroy();
    
    def createImageLoadingThumbnail(self,width,height):
        l = gtk.icon_theme_get_default().list_icons();
        icon = gtk.icon_theme_get_default().load_icon('image-loading',min(width,height)-4,0);
        pixmap = gtk.gdk.Pixmap(gtk.gdk.get_default_root_window(), width, height);
        gc = pixmap.new_gc();
        pixmap.draw_rectangle(gc,True,0,0, width, height);
        pixmap.draw_pixbuf(None,icon,0,0,(width-icon.get_width())/2,(height-icon.get_height())/2);
        self.imageLoadingThumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB,False,8,width,height);
        self.imageLoadingThumbnail.get_from_drawable(pixmap,gtk.gdk.colormap_get_system(),0,0,0,0,width,height);
        return self.imageLoadingThumbnail;
        
    def fileNew(self,widget):    
        newCatalogDialog = gtk.FileChooserDialog("New catalog",
                            self.mainWindow,
                            gtk.FILE_CHOOSER_ACTION_SAVE,
                            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                             gtk.STOCK_SAVE, gtk.RESPONSE_OK));
        newCatalogDialog.set_current_folder(self.currentFolder);
        if newCatalogDialog.run() == gtk.RESPONSE_OK:
            self.currentFolder = newCatalogDialog.get_current_folder();
            file = newCatalogDialog.get_filename();
            if os.path.exists(file):
                os.remove(file);
            self.catalog = catalogs.Catalog(file);
        newCatalogDialog.destroy();
    
    def fileSave(self,widget):
        pass;
    
    def fileSaveAs(self,widget):
        pass;
    
    def fileQuit(self,widget):
        pass;
    
    def importFolder(self,widget):
        importFolderDialog = gtk.FileChooserDialog("Import folder",
                            self.mainWindow,
                            gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
                            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                             gtk.STOCK_ADD, gtk.RESPONSE_OK));
        importFolderDialog.set_select_multiple(True);
        
        importFolderDialog.set_current_folder(self.currentFolder);
        if importFolderDialog.run() == gtk.RESPONSE_OK:
            self.currentFolder = importFolderDialog.get_current_folder();
            self.importDialog.present();
            self.importer = importer.Importer(self.catalog,
                                              self.thumbnailer,
                                              self.callbackImportStatus,
                                              self.callbackImportProgress,
                                              self.callbackImportEnd);
            self.importer.addFolder(importFolderDialog.get_filename());
            importFolderDialog.destroy();
            self.importer.run();
            
    def importFiles(self,widget):
        self.importFilesDialog.set_current_folder(self.currentFolder);
        filter = gtk.FileFilter();
        filter.add_pixbuf_formats();
        filechooser.add_filter(filter);
        filechooser.set_select_multiple(True);
        response = filechooser.run();
        try:
            if response == gtk.RESPONSE_OK:
                i = self.importer.Importer(self.catalog,self.callbackImportStatus,self.callbackImportProgress);
                i.addFolder(filechooser.get_filename())
                i.run();
        finally:
            filechooser.destroy();
            
    def cancelImport(self,widget):
        pass;
            
    def callbackImportStatus(self,message,file):
        self.importStatus.set_text(message);
    
    def callbackImportProgress(self,pos,len):
        self.importProgressBar.set_fraction(float(pos)/float(len));
        if pos == len:
            self.importDialog.hide();
            
    def callbackImportEnd(self,completed):
        self.importDialog.hide();
        self.home();
        self.queueFilesWithoutThumbnails();
        
    def importFile(self,widget):
        filechooser = gtk.FileChooserDialog("Import images",
                            self.mainWindow,
                            gtk.FILE_CHOOSER_ACTION_OPEN,
                            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                             gtk.STOCK_ADD, gtk.RESPONSE_OK));
        filter = gtk.FileFilter();
        filter.add_pixbuf_formats();
        filechooser.add_filter(filter);
        filechooser.set_select_multiple(True);
        response = filechooser.run();
        try:
            if response == gtk.RESPONSE_OK:
                print filechooser.get_filename();
        finally:
            filechooser.destroy();
            
    def importImage(self,file):
        pass;
    
#    def importFolder(self):
#        for root, dirs, files in os.walk('/media/backup/Immagini/Fotografie/2005/200502/20050214'):
#            for name in files:
#                file = os.path.join(root,name);
#                base,ext = os.path.splitext(name);
#                if (ext.lower().endswith('.jpg')):
#                    item = (base,self.thumbnailCache.getThumbnail(file),file);
#                    self.filePositions[file] = self.thumbnailsModel.append(item);
                    
    def on_MainWindow_destroy(self,widget):
        gtk.main_quit(widget);
        
    def home(self):
        self.tagBrowser.get_selection().unselect_all();
        if self.notebookTagger.get_current_page() == 0:
            self.on_tagBrowser_selection_changed(self.tagBrowser.get_selection());
        self.updatePropertyEditorValues();
        
    def workThumbnailer(self):
        return self.thumbnailer.work();
        
    def on_thumbnailsView_scrolled(self,widget):
        if not self.updatingThumbnails:
            range = self.thumbnailsView.get_visible_range();
            if range != self.currentThumbnailsRange:
                self.currentThumbnailsRange = range;
                self.updatingThumbnails = True;
                gobject.idle_add(self.loadVisibleThumbnails);
        
    def loadVisibleThumbnails(self):
        try:
            range = self.thumbnailsView.get_visible_range();
            if range != None:
                (first,),(last,) = range;
                pagesize = last-first+1;
                index = first = max(0,(first/pagesize*pagesize)-pagesize-pagesize);
                len = pagesize*5;
                last = first+len;
#                print "Range: "+str(range)+", updating: "+str(first)+","+str(last);
                iter = self.thumbnailsModel.get_iter((first,));
                while iter != None and len >= 0:
                    item = self.thumbnailsModel.get_value(iter,2);
                    file = item.file;
                    if item.thumbnail == None:
#                        print "Loading: [" +str(index)+" ]" + file;
                        self.thumbnailCache.getThumbnail(file);
                    iter = self.thumbnailsModel.iter_next(iter);
                    len = len-1;
                    index = index+1;
        finally:
            self.updatingThumbnails = False;
            return False;
    
    def on_imageNotebook_switch_page(self,notebook, page, page_num):
        if page_num == 1:
            self.loadImage(self.getCursorFile());
            
    def loadImage(self,file):
            self.imagePixbuf = gtk.gdk.pixbuf_new_from_file(self.catalog.resolveFile(file));
            self.resizedPixbuf = None;
             
    def resizePixmap(self,pixbuf,maxWidth,maxHeight):
        width = pixbuf.get_width();
        height = pixbuf.get_height();
        if (width == maxWidth and height <= maxHeight) or \
           (height == maxHeight and width <= maxWidth):
            return pixbuf;
        newWidth = maxWidth;
        newHeight = maxWidth*height/width;
        if newHeight > maxHeight:
            newWidth = maxHeight*width/height;
            newHeight = maxHeight;
        return pixbuf.scale_simple(newWidth, newHeight, gtk.gdk.INTERP_BILINEAR);

    def on_imageView_expose_event(self,widget,event):
        if self.imagePixbuf != None:
            style = widget.get_style();
            gc = style.fg_gc[gtk.STATE_NORMAL];
            viewWidth,viewHeight = widget.window.get_size();
            if self.resizedPixbuf == None:
                self.resizedPixbuf = self.resizePixmap(self.imagePixbuf,viewWidth,viewHeight);
            width = self.resizedPixbuf.get_width();
            height = self.resizedPixbuf.get_height();
            widget.window.draw_pixbuf(gc, self.resizedPixbuf, 0, 0, (viewWidth-width)/2, (viewHeight-height)/2,width, height)
        pass;
    
    def on_imageView_configure_event(self,widget,event):
        self.resizedPixbuf = None;
        
    def on_thumbnailsView_item_activated(self,widget, path):
        self.imageNotebook.set_current_page(1);
        
    def on_imageView_button_press_event(self,widget,event):
        if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
            self.imageNotebook.set_current_page(0);
            
    def buildTagBrowserModel(self):
        self.tagBrowserModel.clear();
        self.buildTagBrowserModelForProperty('photoshop:Category');
        self.buildTagBrowserModelForProperty('mediapro:Event');
        self.buildTagBrowserModelForProperty('dc:creator');
        self.buildTagBrowserModelForProperty('mediapro:People');
        self.buildTagBrowserModelForProperty('Iptc4xmpCore:Scene');
        self.buildTagBrowserModelForProperty('photoshop:City');
        self.buildTagBrowserModelForProperty('photoshop:Country');
        self.buildTagBrowserModelForProperty('photoshop:State');
    
    def buildTagBrowserModelForProperty(self,propertyName):
        property = xmp.getProperty(propertyName);
        if property != None:
            if self.catalog == None:
                iter = self.tagBrowserModel.append(None,(property,property.getLabelOrName(self.lang),0));
            else:
                count = self.catalog.countFilesWithTag(property.getPrefixedName());
                iter = self.tagBrowserModel.append(None,(property,property.getLabelOrName(self.lang),count));
                values = self.catalog.getTagValues(property.getPrefixedName());
                for (value,count) in values:
                    self.tagBrowserModel.append(iter,(property,value,count));
                    
    def on_tagBrowser_selection_changed(self,selection):
        if self.catalog == None:
            self.showFiles(None);
        else:
            (model,iter) = selection.get_selected();
            if iter == None:
               self.showFiles(self.catalog.getAllFiles());
            else:
                property = model.get_value(iter,0);
                if len(model.get_path(iter)) == 1:
                    self.showFiles(self.catalog.getFilesWithTag(property.getPrefixedName()));
                else:
                    value = self.tagBrowserModel.get_value(iter,1);
                    self.showFiles(self.catalog.getFilesWithTagValue(property.getPrefixedName(),value));
        
    def loadFiles(self):
        self.loadingFiles = True;
        while (self.loadingFilesIndex < len(self.files)):
            self.loadingFilesIndex = self.loadFilesRange(self.loadingFilesIndex,500);
            return True
        self.loadingFiles = False;
        return False
    
    def loadFilesRange(self,start,length):
        i = start;
        self.thumbnailsView.freeze_child_notify();
        try:
            while (length > 0 and i < len(self.files)):
                file, = self.files[i];
                name = os.path.basename(file);
                base,ext = os.path.splitext(name);
                item = self.itemsByFile.get(file);
                if item == None:
                    self.itemsByFile[file] = item = Item(file);
                item.position = i;
                self.thumbnailsModel.append((base[:20],self.imageLoadingThumbnail,item));
                i = i+1;
                length = length-1;
        finally:
            self.thumbnailsView.thaw_child_notify();
        return i;
    
    def showFiles(self,files):
        self.thumbnailsModel.clear();
        self.itemsByFile.clear();
        self.files = files;
        if files != None:
            self.loadingFilesIndex = 0;
            if not self.loadingFiles:
                gobject.idle_add(self.loadFiles);
         
    def toolHome(self,widget):
        self.home();
        
    def toolApply(self,widget):
        pass;
        
    def toolCancel(self,widget):
        self.setPropertyEditorValues(self.catalog.getTags(file));
        
    def setDirty(self,dirty):
        self.dirty = dirty;
        self.toolApply.set_sensitive(dirty);
        self.toolCancel.set_sensitive(dirty);
        
    def on_propertyEditorDirty(self,propertyEditor,dirty):
        if dirty:
            self.setDirty(True);
        else:
            for propertyEditor in self.propertyEditors.itervalues():
                if propertyEditor.isDirty():
                    self.setDirty(True);
                    return;
            self.setDirty(False);
     
    def closing(self,widget,data):
        return False;
            
if __name__ == "__main__":
    gui = GUI()
    gtk.gdk.threads_init();
    gtk.main()
